﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System.Diagnostics.Contracts;

namespace FlitBit.Parse
{
	/// <summary>
	///   Matches repeatedly.
	/// </summary>
	public class RepeatedMatch : Match
	{
		Match _match;
		int _max;

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="match">The rule to match repeatedly</param>
		/// <param name="max">A limit on the number of times the match can occur</param>
		public RepeatedMatch(Match match, int max)
			: base(MatchKind.Exact)
		{
			Contract.Requires(match != null, "match cannot be null");
			Contract.Requires(max >= 0, "max must be positive");

			_match = match;
			_max = max;
		}

		/// <summary>
		///   Determines if the input cursor is positioned at a match.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a match; otherwise <em>false</em>
		/// </returns>
		public override bool At(char[] input, Cursor cursor)
		{
			return _match.At(input, cursor);
		}

		/// <summary>
		///   Determines if the input cursor is positioned at a match.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a match; otherwise <em>false</em>
		/// </returns>
		public override bool At(string input, Cursor cursor)
		{
			return _match.At(input, cursor);
		}

		/// <summary>
		///   Determins if this instace is equal to another.
		/// </summary>
		/// <param name="obj">the other instance</param>
		/// <returns>
		///   <em>true</em> if the two are equal; otherwise <em>false</em>
		/// </returns>
		public override bool Equals(object obj)
		{
			return typeof(RepeatedMatch).IsInstanceOfType(obj)
				&& this.Equals((RepeatedMatch) obj);
		}

		/// <summary>
		///   Gets the an array of the exact character matches at the front.
		/// </summary>
		/// <returns></returns>
		public override CharMatch[] GetExactCharacterMatchesOnFront()
		{
			return _match.GetExactCharacterMatchesOnFront();
		}

		/// <summary>
		///   Gets the instance's hashcode.
		/// </summary>
		/// <returns>the hashcode</returns>
		public override int GetHashCode()
		{
			var prime = 999067; // a random prime
			var h = base.GetHashCode() * prime;
			h ^= _max * prime;
			h ^= _match.GetHashCode() * prime;
			return h;
		}

		/// <summary>
		///   Determines if the input cursor is positioned at a match and if so advances the cursor.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a match; otherwise <em>false</em>
		/// </returns>
		public override bool TryAt(char[] input, ref Cursor cursor)
		{
			if (_match.TryAt(input, ref cursor))
			{
				var count = 1;
				while ((_max == 0 || count < _max)
					&& _match.TryAt(input, ref cursor))
				{
					count++;
				}
				return true;
			}
			return false;
		}

		/// <summary>
		///   Determines if the input cursor is positioned at a match and if so advances the cursor.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a match; otherwise <em>false</em>
		/// </returns>
		public override bool TryAt(string input, ref Cursor cursor)
		{
			if (_match.TryAt(input, ref cursor))
			{
				var count = 1;
				while ((_max == 0 || count < _max)
					&& _match.TryAt(input, ref cursor))
				{
					count++;
				}
				return true;
			}
			return false;
		}

		/// <summary>
		///   Determins if this instace is equal to another.
		/// </summary>
		/// <param name="other">the other instance</param>
		/// <returns>
		///   <em>true</em> if the two are equal; otherwise <em>false</em>
		/// </returns>
		public bool Equals(RepeatedMatch other)
		{
			return other != null
				&& base.Equals((Match) other)
				&& _max == other._max
				&& _match == other._match;
		}
	}
}